/*
#Copyright (c) 2013, Philippe Bordron <philippe.bordron@gmail.com>
#
# This file is part of SIPPER.
#
# DCC is free software: you can redistribute it and/or modify
# it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DCC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU LESSER GENERAL PUBLIC LICENSE for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# along with SIPPER.  If not, see <http://www.gnu.org/licenses/>
*/
package combi.sipper.modeling;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;

import org.jgrapht.DirectedGraph;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;

import combi.sipper.io.InFlatFile;
import combi.wip.sipper.io.Profil;
import combi.wip.sipper.modeling.reactions.ReactionUtil.Orientation;

public class MetabolicNetwork<C,R,E>{

	private Map<C,Set<R>> compoundsSet;
	private Map<C,Set<R>> leftPartSet;
	private Map<C,Set<R>> rightPartSet;
	private Map<R,Set<C>> reactionsSet;
	private Map<R,Orientation> reactionOrientation;
	private Map<R,Set<C>> reactionLeftPart;
	private Map<R,Set<C>> reactionRightPart;
	private Map<R,Set<E>> enzymaticReactions;
	private Map<E,Set<R>> enzymesSet;
	
	private Set<C> tooMuchUtilizedCompounds;
	private int tooMuchUtilizedCompoundsLimit;

	
	public MetabolicNetwork()
	{
		compoundsSet = new HashMap<C,Set<R>>();
		leftPartSet = new HashMap<C,Set<R>>();
		rightPartSet = new HashMap<C,Set<R>>();
		reactionsSet = new HashMap<R,Set<C>>();
		reactionOrientation = new HashMap<R,Orientation>();
		reactionLeftPart = new HashMap<R,Set<C>>();
		reactionRightPart = new HashMap<R,Set<C>>();
		enzymaticReactions = new HashMap<R,Set<E>>();
		enzymesSet = new HashMap<E,Set<R>>();
		tooMuchUtilizedCompounds = new HashSet<C>();
		tooMuchUtilizedCompoundsLimit = 0;
	}

	public boolean containsCompounds(C compound)
	{
		return compoundsSet.containsKey(compound);
	}

	public boolean containsReaction(R reaction)
	{
		return reactionsSet.containsKey(reaction);
	}

	public boolean containsEnzyme(E enzyme)
	{
		return enzymesSet.containsKey(enzyme);
	}

	private void majCompounds(Map<C, Set<R>> map, C key, R value)
	{
		Set<R> values = this.leftPartSet.get(key);
		if (values == null)
		{
			values = new HashSet<R>();
			this.leftPartSet.put(key, values);
		}
		values.add(value);
	}

	public void addCompound(C compound)
	{
		this.compoundsSet.put(compound, new HashSet<R>());
	}

	/**
	 * Add a reaction with its substrats and compounds
	 * @param reaction
	 * @param substrats
	 * @param products
	 */
	public void addReaction(R reaction, Set<C> leftPart, Set<C> rightPart, Orientation orientation)
	{
		this.reactionLeftPart.put(reaction, leftPart);
		for (C c : leftPart)
		{
			majCompounds(leftPartSet, c, reaction);
		}
		this.reactionRightPart.put(reaction, rightPart);
		for (C c : rightPart)
		{
			majCompounds(rightPartSet, c, reaction);
		}
		Set<C> cpds = new HashSet<C>(leftPart);
		cpds.addAll(rightPart);
		this.reactionsSet.put(reaction, rightPart);
		for (C c : cpds)
		{
			majCompounds(compoundsSet, c, reaction);
		}
		reactionOrientation.put(reaction, orientation);
	}

	public void addEnzyme(E enzyme)
	{
		enzymesSet.put(enzyme, new HashSet<R>());
	}


	/**
	 * Associate an enzyme that catalyses a reaction
	 * @param reaction
	 * @param enzyme
	 */
	public void addCatalyse(R reaction, E enzyme)
	{
		Set<E> enz = enzymaticReactions.get(reaction);
		if (enz == null)
		{
			enz = new HashSet<E>();
			enzymaticReactions.put(reaction, enz);
		}
		enz.add(enzyme);

		Set<R> r = enzymesSet.get(enzyme);
		if (r == null)
		{
			r = new HashSet<R>();
			enzymesSet.put(enzyme, r);
		}
		r.add(reaction);
	}

	public Set<C> getCompounds()
	{
		return this.compoundsSet.keySet();
	}

	public Set<R> getReactions()
	{
		return this.reactionsSet.keySet();
	}

	public Set<E> getEzymes()
	{
		return this.enzymesSet.keySet();
	}

	public Orientation getOrientation(R reaction)
	{
		return reactionOrientation.get(reaction);
	}


	public Set<C> getLeftPart(R reaction)
	{
		return reactionLeftPart.get(reaction);
	}


	public Set<C> getRightPart(R reaction)
	{
		return reactionRightPart.get(reaction);
	}


	public Set<C> getSubstrats(R reaction)
	{
		Set<C> result = null;
		switch (reactionOrientation.get(reaction)){
		case LR :
			result = new HashSet<C>(reactionLeftPart.get(reaction));
			break;
		case RL :
			result = new HashSet<C>(reactionRightPart.get(reaction));
			break;
		case BOTH :
		case UNDEFINED : 
			result = new HashSet<C>(reactionsSet.get(reaction));
		}
		return result;
	}

	public Set<C> getProducts(R reaction)
	{
		Set<C> result = null;
		switch (reactionOrientation.get(reaction)){
		case LR :
			result = new HashSet<C>(reactionRightPart.get(reaction));
			break;
		case RL :
			result = new HashSet<C>(reactionLeftPart.get(reaction));
			break;
		case BOTH :
		case UNDEFINED : 
			result = new HashSet<C>(reactionsSet.get(reaction));
		}
		return result;
	}

	public Set<E> getEnzymes(R reaction)
	{
		return enzymaticReactions.get(reaction);
	}

	public Set<R> getReactions(C compound)
	{
		return compoundsSet.get(compound);
	}


	public Set<R> getReactionsAsLeftPart(C compound)
	{
		return leftPartSet.get(compound);
	}


	public Set<R> getReactionsAsRightPart(C compound)
	{
		return rightPartSet.get(compound);
	}


	public Set<R> getCatalysedReactions(E enzyme)
	{
		return enzymesSet.get(enzyme);
	}


	public Set<R> getReactionsAsSubstrat(C compound)
	{
		Set<R> result = new HashSet<R>(leftPartSet.get(compound));
		Set<R> toRemove = new HashSet<R>();
		for (R r : result)
		{
			if(reactionOrientation.get(r) == Orientation.RL)
			{
				toRemove.add(r);
			}
		}
		result.removeAll(toRemove);
		toRemove.clear();
		Set<R> temp = new HashSet<R>(rightPartSet.get(compound));
		for (R r : temp)
		{
			if(reactionOrientation.get(r) != Orientation.LR)
			{
				result.add(r);
			}
		}
		return result;
	}


	public Set<R> getReactionsAsProduct(C compound)
	{
		Set<R> result = new HashSet<R>(leftPartSet.get(compound));
		Set<R> toRemove = new HashSet<R>();
		for (R r : result)
		{
			if(reactionOrientation.get(r) == Orientation.LR)
			{
				toRemove.add(r);
			}
		}
		result.removeAll(toRemove);
		toRemove.clear();
		Set<R> temp = new HashSet<R>(rightPartSet.get(compound));
		for (R r : temp)
		{
			if(reactionOrientation.get(r) != Orientation.RL)
			{
				result.add(r);
			}
		}
		return result;
	}


	private void removeCompoundWithoutConsistency(C compound)
	{
		for (R r : compoundsSet.get(compound))
		{
			reactionsSet.get(r).remove(compound);
			reactionLeftPart.get(r).remove(compound);
			reactionRightPart.get(r).remove(compound);
		}
		compoundsSet.remove(compound).clear();
		leftPartSet.remove(compound).clear();
		rightPartSet.remove(compound).clear();
	}

	/**
	 * remove the compound from the metabolic network and remove the reactions that used it, if the role of product or substrat does not exist
	 * @param compound
	 */
	public void removeCompound(C compound)
	{
		this.removeCompoundWithoutConsistency(compound);
		this.consolidateReactionsSet();
	}

	
	public void setCompoundUtilizationFilter(int limit)
	{
		tooMuchUtilizedCompoundsLimit = limit;
		tooMuchUtilizedCompounds.clear();
		if (limit > 0)
		{
			for (Entry<C, Set<R>> c : compoundsSet.entrySet())
			{
				if (c.getValue().size() >= limit)
				{
					tooMuchUtilizedCompounds.add(c.getKey());
				}
			}
		}
	}

	private void consolidateReactionsSet()
	{
		Set<R> toRemove = new HashSet<R>();
		Set<C> toAdjust = new HashSet<C>();
		for (Entry<R, Set<C>> entry : reactionsSet.entrySet())
		{
			if(entry.getValue().isEmpty())
			{
				toRemove.add(entry.getKey());
			}
		}
		for (Entry<R, Set<C>> entry : reactionLeftPart.entrySet())
		{
			if(entry.getValue().isEmpty())
			{
				toRemove.add(entry.getKey());
				if (reactionRightPart.containsKey(entry.getKey()))
				{
					Set<C> cpds = reactionRightPart.get(entry.getKey());
					toAdjust.addAll(cpds);
				}
			}
		}
		for (Entry<R, Set<C>> entry : reactionRightPart.entrySet())
		{
			if(entry.getValue().isEmpty())
			{
				toRemove.add(entry.getKey());
				if (reactionLeftPart.containsKey(entry.getKey()))
				{
					Set<C> cpds = reactionLeftPart.get(entry.getKey());
					toAdjust.addAll(cpds);
				}
			}
		}

		for (R r: toRemove)
		{
			removeReaction(r);
		}
		toRemove.clear();

		for (C c: toAdjust)
		{
			compoundsSet.remove(c);
		}
		toAdjust.clear();
	}

	public void removeReaction(R reaction)
	{
		for (E enz : enzymaticReactions.get(reaction))
		{
			enzymesSet.get(enz).remove(reaction);
		}
		enzymaticReactions.remove(reaction).clear();
		reactionLeftPart.remove(reaction).clear();
		reactionRightPart.remove(reaction).clear();
	}

	public void removeEnzyme(E enzyme)
	{
		for (R r : enzymesSet.get(enzyme))
		{
			enzymaticReactions.get(r).remove(enzyme);
		}
		enzymesSet.remove(enzyme).clear();
	}


	public DirectedGraph<R,DefaultEdge> generateReactionNetwork()
	{
		DirectedGraph<R,DefaultEdge> result = new DefaultDirectedGraph<R,DefaultEdge>(DefaultEdge.class);
		Set<C> cut = null;
		for (R r : this.getReactions())
		{
			result.addVertex(r);
		}
		for (R x : this.getReactions())
		{
			for (R y : this.getReactions())
			{
				if (x != y)
				{
					cut = new HashSet<C>(this.getSubstrats(x));
					cut.retainAll(this.getProducts(y));
					cut.removeAll(this.tooMuchUtilizedCompounds);// take in account the limit
					if (!cut.isEmpty())
					{
						result.addEdge(x, y);
					}
					cut.clear();
				}
			}
		}
		return result;
	}


	public DirectedGraph<C,DefaultEdge> generateCompoundsNetwork()
	{
		// TODO
		return null;
	}

	public DirectedGraph<E,DefaultEdge> generateEnzymeNetwork()
	{
		// TODO
		return null;
	}


	public static void main(String[] args) {

		String profilFile = args[0];
		try {
			Profil ecocycFiles = Profil.createProfil(profilFile);
			Map<String, Map<String, Collection<String>>> compounds = new HashMap<String, Map<String, Collection<String>>>();
			Map<String, Map<String, Collection<String>>> reactions = new HashMap<String, Map<String, Collection<String>>>();
			Map<String, Map<String, Collection<String>>> enzReactions = new HashMap<String, Map<String, Collection<String>>>();
			Map<String, Map<String, Collection<String>>> proteins = new HashMap<String, Map<String, Collection<String>>>();
			
			InFlatFile compoundsIn = new InFlatFile(ecocycFiles.get("ecocyc_files_path")+ ecocycFiles.get("compounds"));
			InFlatFile proteinsIn = new InFlatFile(ecocycFiles.get("ecocyc_files_path")+ ecocycFiles.get("proteins"));
			InFlatFile reactionsIn = new InFlatFile(ecocycFiles.get("ecocyc_files_path")+ ecocycFiles.get("reactions"));
			InFlatFile enzReactionsIn = new InFlatFile(ecocycFiles.get("ecocyc_files_path")+ ecocycFiles.get("enzrxns"));
			
			long oldtime = System.currentTimeMillis();
			System.out.println("Loading data from the file " + ecocycFiles.get("ecocyc_files_path")+ ecocycFiles.get("compounds"));
			compounds = compoundsIn.readFile();
			long newtime = System.currentTimeMillis();
			System.out.println("the process tooks " + (newtime - oldtime) + "ms");
			System.out.println(compounds.entrySet().size() + " elements were loaded");
			
			oldtime = newtime;
			System.out.println("Loading data from the file " + ecocycFiles.get("ecocyc_files_path")+ ecocycFiles.get("reactions"));
			reactions = reactionsIn.readFile();
			newtime = System.currentTimeMillis();
			System.out.println("the process tooks " + (newtime - oldtime) + "ms");
			System.out.println(reactions.entrySet().size() + " elements were loaded");

			oldtime = newtime;
			System.out.println("Loading data from the file " + ecocycFiles.get("ecocyc_files_path")+ ecocycFiles.get("proteins"));			
			proteins = proteinsIn.readFile();
			newtime = System.currentTimeMillis();
			System.out.println("the process tooks " + (newtime - oldtime) + "ms");
			System.out.println(proteins.entrySet().size() + " elements were loaded");

			oldtime = newtime;
			System.out.println("Loading data from the file " + ecocycFiles.get("ecocyc_files_path")+ ecocycFiles.get("enzrxns"));			
			enzReactions = enzReactionsIn.readFile();
			newtime = System.currentTimeMillis();
			System.out.println("the process tooks " + (newtime - oldtime) + "ms");
			System.out.println(enzReactions.entrySet().size() + " elements were loaded");

			
			
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
